﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
using DeepZoomPivotConstructor.ErrorAndLoggingInterfaces;
using DeepZoomPivotConstructor.PivotLib;
using DeepZoomPivotConstructor.VisualCacheXMLDirectory;
using PaperPluginUtilities;
using PaperRenderingEngine;
using RenderEngine;

namespace PivotPhysicsPlotsGenerator
{
    class Program
    {
        private static VisualCacheDirectoryDB VisualCache;

        private static IErrorLogger Logger;

        /// <summary>
        /// Creates a pivot library of all the physics plots we can get our hands on
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            ///
            /// Parse the command line.
            /// 

            DirectoryInfo outputDir = new DirectoryInfo(System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\PhysicsPlotsTest");
            List<FileInfo> fileInfoFiles = new List<FileInfo>();
            foreach (var arg in args)
            {
                if (arg.StartsWith("/"))
                {
                    var splitSwitch = arg.Split(':');
                    switch (splitSwitch[0])
                    {
                        case "/output":
                            outputDir = new DirectoryInfo(arg.Substring(8));
                            break;

                        default:
                            break;
                    }
                }
                else
                {
                    fileInfoFiles.Add(new FileInfo(arg));
                }
            }

            ///
            /// Turn the config files into the plot sources list.
            /// 

            XmlSerializer trans = new XmlSerializer(typeof(List<PlotLocationMetadata>));
            var plotSources = fileInfoFiles.SelectMany(f =>
                {
                    if (!f.Exists)
                    {
                        Logger.LogMessage("Main", "Unable to open plot config file " + f.FullName);
                        return Enumerable.Empty<PlotLocationMetadata>();
                    }

                    using (var reader = f.OpenText())
                    {
                        var ps = (List<PlotLocationMetadata>)trans.Deserialize(reader);
                        reader.Close();
                        return ps;
                    }
                });

            ///
            /// The cache to hold already-done results.
            /// 

            VisualCache = new VisualCacheDirectoryDB(new DirectoryInfo(outputDir.FullName + Path.DirectorySeparatorChar + "cacheDB"));

            ///
            /// Next, setup the Rendering interfaces, including the database of previously rendered items.
            /// We also have to boot up MEF at this point to get all the plug-ins working.
            /// 

            DirectoryInfo tempDir = new DirectoryInfo(Path.GetTempPath() + Path.DirectorySeparatorChar + Path.GetRandomFileName());
            tempDir.Create();

            ///
            /// Logger needs to hang about too - setup one that writes to files
            /// 

            if (!outputDir.Exists)
            {
                outputDir.Create();
            }
            Logger = new FileLogger(new FileInfo(outputDir.FullName + "\\PivotPhysicsPlotsGenerator.txt"));
            Logger.LogMessage("Main", "Start up...");

            SetupRenderingBackend(tempDir, Logger);

            ///
            /// Next, add the render engine to the MEF pool.
            /// 

            var paperRender = new PaperRenderingEngine.RenderEngine();
            WebAccess wa = new WebAccess();
            wa.HTTPAgent = "Physics Plots Pivot Scanner";
            container.ComposeParts(new object[] { wa, paperRender });

            ///
            /// Ok, get the plots and process them! WHen we grab them we perform several checks:
            /// - Not null (duh)
            /// - There isn't more than one image associted with this. This latter guy is an attempt to get rid of the notes that are everywhere
            ///   that are often 20 pages long or something like that. Note that this number might be zero if there is a VIsualItemPlaceHolder
            ///   that has been returned!
            /// 

            var plots = plotSources.SelectMany(pinfo =>
                paperRender.Render(pinfo.renderEngineName, pinfo.renderAddress)
                .RunThisAtStart(() => Logger.LogMessage("Main", "Starting " + pinfo.renderEngineName + " on '" + pinfo.renderAddress + "'"))
                .TrackErrors("Error while using " + pinfo.renderEngineName + " to render '" + pinfo.renderAddress + "'")
                .Apply(img => pinfo.MetaDataToAdd.Apply(meta => img.Add(meta.tag, meta.val)).Last())
                );

            PivotCollection plotColleciton = new PivotCollection();
            try
            {
                Logger.LogMessage("Main", "Finding and Rendering to Visuals all the images");
                foreach (var item in plots)
                {
                    if (item.Image == null)
                    {
                        Logger.LogMessage("Main", "Null image for a pivot plot - " + item.Name + " -- " + item.Description);
                    }
                    else
                    {
                        int sgCount = item.Image.SGNodeList.Count();
                        if (item.Image.SGNodeList.Count() > 1)
                        {
                            Logger.LogMessage("Main", "Image for pivot plot has more than one page (" + sgCount.ToString() + " images - all ignored) - " + item.Name + " -- " + item.Description);
                        }
                        else
                        {
                            plotColleciton.Add(item);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Logger.LogMessage("Main", "Failed to iterate over all possible items in our collection!", e);
            }

            Logger.LogMessage("Main", "Finished rendering images to visuals, setup for rendering the visuals to DZI's");

            ///
            /// We want the caption searchable,  but not to really get in the way of the UI.
            /// So we put it here.
            /// 

            SetFacetSearchableOnly(plotColleciton, "Caption");
            SetFacetSearchableOnly(plotColleciton, "Abstract");

            ///
            /// We have them all. Render it (using the database, of course).
            /// 

            try
            {
                PivotProcessor pproc = new PivotProcessor(Logger, VisualCache);
                Logger.LogMessage("Main", "Rendering Visuals to DZI's");
                pproc.CreatePivotCollection(plotColleciton, outputDir, "Physics Plots (" + DateTime.Now.ToShortDateString() + ")", ClassifyByDate);

                ///
                /// And get back the list of collections we had from the classes we defined (i.e. what was used).
                ///

                var classes = from c in pproc.ClassesUsed
                              orderby c descending
                              select c;

                var classesOutput = new FileInfo(outputDir + "\\classes.txt");
                using (var output = classesOutput.CreateText())
                {
                    foreach (var item in classes)
                    {
                        output.WriteLine(item);
                    }
                }
            }
            catch (Exception e)
            {
                Logger.LogMessage("Main", "Failed to create the pivot collection", e);
            }

            ///
            /// Clean up
            /// 

            Logger.LogMessage("Main", "Cleaning up the temp directory and deleting files");
            tempDir.Delete(true);
            Logger.LogMessage("Main", "Done. Bye!");
        }

        /// <summary>
        /// How recent must a plot be to be considered "recent"?
        /// </summary>
        private static TimeSpan _recentInterval = TimeSpan.FromDays(31 * 4);

        /// <summary>
        /// Find the date facet and return teh year as a string - so we can partition up the stuff we are writing out!
        /// </summary>
        /// <param name="pi"></param>
        /// <returns></returns>
        private static string[] ClassifyByDate(PivotImage pi)
        {
            PivotMetadataDateTime dt = pi.GetMetadata<PivotMetadataDateTime>("Date").FirstOrDefault();
            if (dt == null)
            {
                return new string[] { "No Date" };
            }

            if ((DateTime.Now - dt.TheTime) < _recentInterval)
            {
                return new string[] { dt.TheTime.Year.ToString(), "Recent" };
            }

            return new string[] { dt.TheTime.Year.ToString() };
        }

        private static void SetFacetSearchableOnly(PivotCollection plotColleciton, string c)
        {
            plotColleciton.AddCategory(c);
            plotColleciton.FacetCatagories[c].IsMetaDataVisible = false;
            plotColleciton.FacetCatagories[c].IsFilterVisible = false;
            plotColleciton.FacetCatagories[c].IsWordWheelVisible = true;
        }

        /// <summary>
        /// We will need to export a few defaults for the rendering sub-system.
        /// </summary>
        private class RenderSettings
        {
            [Export("RenderingDPI", typeof(int))]
            public int DPI = 300;

            /// <summary>
            /// The agent string we use in URI requests!
            /// </summary>
            [Export("HttpAgentString")]
            public string HttpAgentString = "Pivot Physics Plot Render";

            [Export("RenderingTempDir")]
            public DirectoryInfo RenderingTempDir;

            [Export]
            public IErrorLogger ErrorLogger;
        }

        /// <summary>
        /// The container on which we will be adding our parts, etc.
        /// </summary>
        private static CompositionContainer container = null;

        /// <summary>
        /// Get the rendering sub-system ready to go!
        /// This also means getting MEF going!
        /// </summary>
        private static void SetupRenderingBackend(DirectoryInfo tempDir, IErrorLogger logger)
        {
            ///
            /// Setup the catalog we will be using
            /// 

            var catalog = new AggregateCatalog();
            container = new CompositionContainer(catalog);
            CompositionBatch batch = new CompositionBatch();

            ///
            /// Setup the basic stuff render engine and a layout helper.
            /// 

            batch.AddPart(new RenderControl(VisualCache));
            batch.AddPart(new LayoutHelper());

            ///
            /// Add the cache that tracks what we've done already
            /// 

            batch.AddPart(VisualCache);

            ///
            /// Settings that various render objects sort-of need.
            /// 

            RenderSettings rs = new RenderSettings();
            rs.DPI = 150;
            rs.RenderingTempDir = tempDir;
            rs.ErrorLogger = logger;
            batch.AddPart(rs);

            ///
            /// Next, get all the directories that might have plug-ins.
            /// 

            DirectoryInfo localPlugins = new DirectoryInfo("..\\..\\..\\LocalPlugins\\Debug");
            catalog.Catalogs.Add(new DirectoryCatalog(localPlugins.FullName));

            ///
            /// Ok -- ready to hook everyone up!
            /// 

            container.Compose(batch);
        }
    }
}
